﻿using Gentings.Data;
using Microsoft.Extensions.Caching.Memory;
using System.Collections.Concurrent;

namespace Gentings.Extensions.Tagables
{
    /// <summary>
    /// 标签管理接口。
    /// </summary>
    /// <typeparam name="TTag">标签类型。</typeparam>
    /// <typeparam name="TTagIndex">标签索引类型。</typeparam>
    public abstract class TagManager<TTag, TTagIndex> : CachableObjectManager<TTag>, ITagManager<TTag, TTagIndex>
        where TTag : TagBase, new()
        where TTagIndex : TagIndexBase, new()
    {
        /// <summary>
        /// 初始化类<see cref="TagManager{TTag, TTagIndex}"/>。
        /// </summary>
        /// <param name="context">标签数据库操作上下文。</param>
        /// <param name="cache">缓存接口。</param>
        protected TagManager(IDbContext<TTag> context, IMemoryCache cache)
            : base(context, cache)
        {
        }

        private static readonly string CacheKeyByName = $"tags[{typeof(TTag)}]";
        /// <summary>
        /// 刷新缓存。
        /// </summary>
        public override void Refresh()
        {
            base.Refresh();
            Cache.Remove(CacheKeyByName);
        }

        private ConcurrentDictionary<string, int> LoadNamedIds()
        {
            return Cache.GetOrCreate(CacheKeyByName, ctx =>
            {
                ctx.SetDefaultAbsoluteExpiration();
                var tags = Context.Fetch();
                return new ConcurrentDictionary<string, int>(tags.ToDictionary(x => x.Name!, x => x.Id), StringComparer.OrdinalIgnoreCase);
            });
        }

        private Task<ConcurrentDictionary<string, int>> LoadNamedIdsAsync()
        {
            return Cache.GetOrCreateAsync(CacheKeyByName, async ctx =>
            {
                ctx.SetDefaultAbsoluteExpiration();
                var tags = await Context.FetchAsync();
                return new ConcurrentDictionary<string, int>(tags.ToDictionary(x => x.Name!, x => x.Id), StringComparer.OrdinalIgnoreCase);
            });
        }

        /// <summary>
        /// 通过名称获取标签Id。
        /// </summary>
        /// <param name="name">标签名称。</param>
        /// <returns>返回标签Id。</returns>
        public virtual int Find(string name)
        {
            var cached = LoadNamedIds();
            cached.TryGetValue(name, out int id);
            return id;
        }

        /// <summary>
        /// 通过名称获取标签Id。
        /// </summary>
        /// <param name="name">标签名称。</param>
        /// <returns>返回标签Id。</returns>
        public virtual async Task<int> FindAsync(string name)
        {
            var cached = await LoadNamedIdsAsync();
            cached.TryGetValue(name, out int id);
            return id;
        }

        /// <summary>
        /// 获取或者创建标签实例，并且返回标签对应的Id列表。
        /// </summary>
        /// <param name="tags">标签名称集合。</param>
        /// <param name="separator">分隔符。</param>
        /// <returns>返回标签对应的Id列表。</returns>
        public virtual List<int> GetOrCreate(string tags, char separator = ',')
        {
            var names = tags.Split(new[] { separator }, StringSplitOptions.RemoveEmptyEntries)
                .Select(x => x.Trim())
                .Distinct(StringComparer.OrdinalIgnoreCase)
                .ToArray();
            var ids = new List<int>();
            var unames = new List<string>();
            var cached = LoadNamedIds();
            foreach (var name in names)
            {
                if (cached.TryGetValue(name, out var id))
                    ids.Add(id);
                else
                    unames.Add(name);
            }
            if (unames.Count > 0)
            {
                Context.BeginTransaction(db =>
                {
                    foreach (var name in unames)
                    {
                        var current = db.Find(x => x.Name == name);
                        if (current == null)
                        {
                            current = new TTag { Name = name };
                            if (!db.Create(current))
                                return false;
                        }
                        ids.Add(current.Id);
                    }
                    return true;
                });
            }
            return ids;
        }

        /// <summary>
        /// 获取或者创建标签实例，并且返回标签对应的Id列表。
        /// </summary>
        /// <param name="tags">标签名称集合。</param>
        /// <param name="separator">分隔符。</param>
        /// <returns>返回标签对应的Id列表。</returns>
        public virtual async Task<List<int>> GetOrCreateAsync(string tags, char separator = ',')
        {
            var names = tags.Split(new[] { separator }, StringSplitOptions.RemoveEmptyEntries)
                .Select(x => x.Trim())
                .Distinct(StringComparer.OrdinalIgnoreCase)
                .ToArray();
            var ids = new List<int>();
            var unames = new List<string>();
            var cached = await LoadNamedIdsAsync();
            foreach (var name in names)
            {
                if (cached.TryGetValue(name, out var id))
                    ids.Add(id);
                else
                    unames.Add(name);
            }
            if (unames.Count > 0)
            {
                await Context.BeginTransactionAsync(async db =>
                {
                    foreach (var name in unames)
                    {
                        var current = await db.FindAsync(x => x.Name == name);
                        if (current == null)
                        {
                            current = new TTag { Name = name };
                            if (!await db.CreateAsync(current))
                                return false;
                        }
                        ids.Add(current.Id);
                    }
                    return true;
                });
            }
            return ids;
        }

        /// <summary>
        /// 执行统计清理服务。
        /// </summary>
        public virtual async Task ExecuteAsync()
        {
            var tagName = Context.EntityType.Table;
            var indexName = Context.As<TTagIndex>().EntityType.Table;
            await Context.ExecuteNonQueryAsync($@"UPDATE {tagName}
SET[Count] = b.[Count]
FROM(SELECT TagId, COUNT(IndexedId) as [Count] FROM {indexName} GROUP BY TagId)  b
WHERE b.TagId = {tagName}.Id; ");
        }
    }
}
